﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Djs.Neuronic.Medium;
using Djs.Neuronic.Core;

namespace Djs.Neuronic.Brain
{
    public class DataTableCls<T> where T : class, IRecord, new()
    {
        #region Konstrukce
        public DataTableCls(CreatorCls creator)
        {
            this._Init(creator);
            this.AcceptSearchers(null);
        }
        public DataTableCls(CreatorCls creator, params ISearcher<T>[] searchers)
        {
            this._Init(creator);
            this.AcceptSearchers(searchers);
        }
        private void _Init(CreatorCls creator)
        {
            this.Creator = creator;
            this.TableId = new Dictionary<int, Wrapper<T>>();
            this.ClassInfo = this.ClassDeclaration.GetClassInfo(typeof(T));
            this.LockInit();
        }
        protected CreatorCls Creator;
        protected DatabaseCls Database { get { return this.Creator.Database; } }
        protected MemoriesCls Memories { get { return this.Creator.Memories; } }
        protected ClassDeclarationCls ClassDeclaration { get { return this.Creator.ClassDeclaration; } }
        #endregion
        #region Základní úložiště a vyhledávání dat podle ID, pouze v paměti (bez donačítání)
        protected T Find(int id)
        {
            T result;
            if (!this.TryFind(id, out result))
                throw new NeuroDataNotFoundException(typeof(T), id);
            return result;
        }
        protected bool TryFind(int id, out T result)
        {
            Wrapper<T> wrapper;
            if (!this.TableId.TryGetValue(id, out wrapper))
            {
                this.LoadDbRecord(id);
                this.TableId.TryGetValue(id, out wrapper);
            }
            if (wrapper == null)
            {
                result = null;
                return false;
            }
            result = wrapper.Record;
            return (result != null);
        }
        /// <summary>
        /// Tabulka dat podle ID.
        /// Současně slouží jako objekt pro zamykání (lock) v situacích, kdy se chystáme do tabulky přidávat nebo odebírat data.
        /// </summary>
        protected Dictionary<int, Wrapper<T>> TableId;
        /// <summary>
        /// Počet záznamů v tabulce, včetně dat hledaných a nenalezených
        /// </summary>
        public int TableCount { get { return this.TableId.Count; } }
        protected class Wrapper<W> where W : T
        {
            public Wrapper(int id, W record)
            {
                this.Id = id;
                this.Record = record;
            }
            /// <summary>
            /// Vizualizace
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return "Id=" + this.Id.ToString() + "; Record=" + (this.Record == null ? "null" : this.Record.ToString());
            }
            public int Id { get; private set; }
            public W Record { get; private set; }
        }
        /// <summary>
        /// Třída nápomocná při získávání dat z paměti / z databáze / při vytváření nových záznamů
        /// </summary>
        protected class ObtainItem<K, D> where D : T
        {
            public ObtainItem(K key, D data)
            {
                this.Key = key;
                this.Data = data;
            }
            public override string ToString()
            {
                return "Key: " + this.Key.ToString() + "; Data: " + (this.Found ? this.Data.ToString() : "[not found]");
            }
            /// <summary>
            /// Klíč
            /// </summary>
            public K Key { get; private set; }
            /// <summary>
            /// Data
            /// </summary>
            public D Data { get; set; }
            /// <summary>
            /// Data jsou nalezena (this.Data není null)
            /// </summary>
            public bool Found { get { return (this.Data != null); } }
        }

        #endregion
        #region ISearchers
        private void AcceptSearchers(ISearcher<T>[] searchers)
        {
            this.Searchers = new Dictionary<string, ISearcher<T>>();
            this.Tables = new Dictionary<string, Dictionary<T, T>>();
            if (searchers == null || searchers.Length == 0)
                return;

            foreach (ISearcher<T> searcher in searchers)
            {
                if (searcher != null)
                {
                    string typeName = searcher.GetType().NamespaceName();
                    this.Searchers.Add(typeName, searcher);
                    this.Tables.Add(typeName, new Dictionary<T, T>());
                }
            }
        }
        protected Dictionary<string, ISearcher<T>> Searchers;
        protected Dictionary<string, Dictionary<T, T>> Tables;
        #endregion
        #region Načítání dat, spolupráce s ClassDeclaration
        /// <summary>
        /// Pokusí se z databáze načíst záznam daného ID.
        /// Před přidáním zamkne tabulku.
        /// </summary>
        /// <param name="id"></param>
        protected void LoadDbRecord(int id)
        {
            string commandText = this.ClassInfo.GetSqlSelect(id);
            T record = this.Database.ReadRecord<T>(commandText);
            this.AddRecord(id, record);
        }
        /// <summary>
        /// Pokusí se z databáze načíst záznam daného ID.
        /// Neřeší zámky!
        /// </summary>
        /// <param name="id"></param>
        protected void LoadDbRecordNoLock(int id)
        {
            string commandText = this.ClassInfo.GetSqlSelect(id);
            T record = this.Database.ReadRecord<T>(commandText);
            this.AddRecordNoLock(id, record);
        }
        /// <summary>
        /// Pokusí se z databáze načíst záznamy daných ID.
        /// Před načítáním zamkne tabulku.
        /// </summary>
        /// <param name="id"></param>
        protected void LoadDbRecords(IEnumerable<int> ids)
        {
            List<string> commandTexts = this.ClassInfo.GetSqlSelects(ids);
            this.LoadDbRecordsWithCommands(commandTexts);
        }
        /// <summary>
        /// Pokusí se z databáze načíst záznamy daných ID.
        /// Neřeší zámky!
        /// </summary>
        /// <param name="id"></param>
        protected void LoadDbRecordsNoLock(IEnumerable<int> ids)
        {
            List<string> commandTexts = this.ClassInfo.GetSqlSelects(ids);
            this.LoadDbRecordsWithCommandsNoLock(commandTexts);
        }
        /// <summary>
        /// Z databáze načte záznamy za pomoci dodané sady SQL příkazů.
        /// SQL příkazy mohou vracet řadu záznamuů, nemusí vracet jen jeden záznam (List příkazů je použit proto, aby SQL příkaz nebyl delší než typicky 16KB).
        /// Před načítáním zamkne tabulku.
        /// </summary>
        /// <param name="commandTexts"></param>
        protected void LoadDbRecordsWithCommands(List<string> commandTexts)
        {
            
            using (Lock())
            {
                this.LoadDbRecordsWithCommandsNoLock(commandTexts);
            }
        }
        /// <summary>
        /// Z databáze načte záznamy za pomoci dodané sady SQL příkazů.
        /// SQL příkazy mohou vracet řadu záznamuů, nemusí vracet jen jeden záznam (List příkazů je použit proto, aby SQL příkaz nebyl delší než typicky 16KB).
        /// Neřeší zámky!
        /// </summary>
        /// <param name="commandTexts"></param>
        protected void LoadDbRecordsWithCommandsNoLock(List<string> commandTexts)
        {
            foreach (string commandText in commandTexts)
            {
                List<T> records = this.Database.ReadList<T>(commandText);
                this.AddRecordsNoLock(records);
            }
        }
        /// <summary>
        /// Přidá dané záznamy do paměťové tabulky.
        /// Před přidáním zamkne tabulku.
        /// </summary>
        /// <param name="records"></param>
        protected void AddRecords(IEnumerable<T> records)
        {
            using (Lock())
            {
                this.AddRecordsNoLock(records);
            }
        }
        /// <summary>
        /// Přidá dané záznamy do paměťové tabulky.
        /// Neřeší zámky!
        /// </summary>
        /// <param name="records"></param>
        protected void AddRecordsNoLock(IEnumerable<T> records)
        {
            foreach (T record in records)
                this.AddRecordNoLock(record.Id, record);
        }
        /// <summary>
        /// Přidá záznam daného ID do paměťové tabulky, pokud v tabulce dosud neexistuje.
        /// Před přidáním zamkne tabulku.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="record"></param>
        protected void AddRecord(int id, T record)
        {
            if (!this.TableId.ContainsKey(id))
            {
                using (Lock())
                {
                    this.AddRecordNoLock(id, record);
                }
            }
        }
        /// <summary>
        /// Přidá záznam daného ID do paměťové tabulky.
        /// Neřeší zámky!
        /// </summary>
        /// <param name="id"></param>
        /// <param name="record"></param>
        protected void AddRecordNoLock(int id, T record)
        {
            if (!this.TableId.ContainsKey(id))
            {
                this.TableId.Add(id, new Wrapper<T>(id, record));
                if (record != null)
                    this.RecordAddAfter(id, record);
            }
        }
        /// <summary>
        /// Háček pro potomky.
        /// Do paměťové tabulky (this.TableId) byl přidán nový záznam.
        /// Pokud potomek eviduje k záznamům nějaké indexy, zde by si měl přidat záznam do indexu.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="record"></param>
        protected virtual void RecordAddAfter(int id, T record)
        { }
        /// <summary>
        /// Informace o třídě
        /// </summary>
        protected ClassInfo ClassInfo { get; private set; }
        #endregion
        #region Ukládání dat do DataTable a do databáze
        /// <summary>
        /// Uloží daný záznam do databáze a do tabulky.
        /// Pokud je to nový záznam (jehož Id == 0), pak jej INSERTUJE do databáze.
        /// Pokud je to existující záznam, a je změněn, UPDATUJE jej do databáze.
        /// Pokud není přítomen v paměti, vloží jej do paměti (pokud v paměti je, chybu nehodí).
        /// </summary>
        /// <param name="data"></param>
        public void Add(T data)
        {
            if (this.NeedSaveRecordToDatabase(data) || this.NeedAddRecordToTable(data))
            {   // Zámek dáme jen tehdy, když se záznam bude ukládat do databáze nebo do tabulky...
                using (Lock())
                {
                    this.AddNoLock(data);
                }
            }
        }
        /// <summary>
        /// Uloží daný záznam do databáze a do tabulky.
        /// Pokud je to nový záznam (jehož Id == 0), pak jej INSERTUJE do databáze.
        /// Pokud je to existující záznam, a je změněn, UPDATUJE jej do databáze.
        /// Pokud není přítomen v paměti, vloží jej do paměti (pokud v paměti je, chybu nehodí).
        /// </summary>
        /// <param name="data"></param>
        public void AddNoLock(T data)
        {
            if (data.Id == 0)
            {
                this.ClassInfo.Save(data);
                this.AddRecordNoLock(data.Id, data);
            }
            else
            {
                if (data.IsDirty)
                    this.ClassInfo.Save(data);
                if (!this.TableId.ContainsKey(data.Id))
                    this.AddRecordNoLock(data.Id, data);
            }
        }
        /// <summary>
        /// Vrátí true, pokud daný záznam je třeba uložit do databáze
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private bool NeedSaveRecordToDatabase(T data)
        {
            return (data != null && (data.Id == 0 || data.IsDirty));
        }
        /// <summary>
        /// Vrátí true, pokud daný záznam je třeba vložit do paměti
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private bool NeedAddRecordToTable(T data)
        {
            return (data != null && (data.Id != 0 && !this.TableId.ContainsKey(data.Id)));
        }
        #endregion
        #region Zámek pro multithread-změny
        /// <summary>
        /// Tato metoda zajistí, že v oblasti dané using blokem bude vždy jen jeden thread.
        /// Použití: using (Lock()) { akce; }
        /// Lock se používá pro oblasti, kde dochází ke změně dat v tabulkách.
        /// Používá se proto, aby více různých threadů nemohlo najednou provádět: donačítání z databáze + vytváření nových záznamů + generování jejich ID.
        /// Hledat data v paměti je možno i v době Locku, ale doplňování nenačtených nebo nových dat nikoliv.
        /// Lock je třeba používat v usingu, aby se na konci provedl Unlock.
        /// </summary>
        /// <returns></returns>
        protected IDisposable Lock()
        {
            return new TableLockCls(this);
        }
        /// <summary>
        /// inicace semaforu
        /// </summary>
        private void LockInit()
        {
            this.Semaphore = new System.Threading.SemaphoreSlim(1, 1);
        }
        /// <summary>
        /// akce tabulky na konci locku. Typicky dokončení práce s načtenými daty.
        /// Volá Flush().
        /// </summary>
        private void LockRelease()
        { }
        /// <summary>
        /// privátní semafor pro řízení přístupu do úzkého hrdla
        /// </summary>
        private System.Threading.SemaphoreSlim Semaphore;
        /// <summary>
        /// Třída IDisposable pro řízení locku, za pomoci semaforu (this.DataTable.Semaphore).
        /// Tato třída sama řídí semafor (v konstruktoru má this.DataTable.Semaphore.Wait(10000), v Dispose má this.DataTable.Semaphore.Release()).
        /// Před uvolněním semaforu volá this.DataTable.LockRelease().
        /// </summary>
        protected class TableLockCls : IDisposable
        {
            public TableLockCls(DataTableCls<T> dataTable)
            {
                this.DataTable = dataTable;
                this.DataTable.Semaphore.Wait(10000);
            }
            protected DataTableCls<T> DataTable;
            void IDisposable.Dispose()
            {
                this.DataTable.LockRelease();
                this.DataTable.Semaphore.Release();
            }
        }
        #endregion

        public T this[int id]
        {
            get { return this.Find(id); }
        }

        public List<T> Search()
        {
            List<T> result = new List<T>();
            result.Add(this.Creator.ClassDeclaration.Classes[""].CreateInstance() as T);
            return result;
        }

        public T TryFind(int id)
        {
            T result;
            if (this.TryFind(id, out result))
                return result;
            return default(T);
        }
    }
    /// <summary>
    /// Interface, který popisuje objekt typu Vyhledávač.
    /// Vyhledávač jednak implementuje IEqualityComparer = slouží pro určení klíče do Dictionary pro ukládání záznamů své třídy pro jednoznačné vyhledávání.
    /// Dále vyhledávač obsahuje funkce, které dovolí vyhledat záznamy v databázi podle jiných hledisek, než klíč ID (typicky hledá podle textu, podle HASH hodnoty záznamu, ...).
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ISearcher<T> : IEqualityComparer<T> where T : class, IRecord, new()
    {

    }

}
